package com.sunday.common.netty.e1_Hello_World;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * https://zhuanlan.zhihu.com/p/657817791
 * <p>
 * Discards any incoming data.
 * <p>
 * 核心组件
 * Netty的核心组件包括三类：
 * <p>
 * 引导类
 * 针对网络抽象的组件
 * 管理数据流及执行应用程序处理逻辑的组件
 * <p>
 * 2.1 引导类
 * Bootstrap: 用于客户端，连接到远程主机和端口，1个EventLoopGroup
 * ServerBootstrap: 用于服务器，绑定到一个本地端口，2个EventLoopGroup
 * <p>
 * 2.2 针对网络抽象的组件：
 * Channel: 对应Socket。基本的IO操作(bind(), connet(), read(), write()), 依赖于底层网络传输，大大降低了直接使用Socket的复杂性。Channel接口常用的实现类有：EmbeddedChannel, LocalServerChannel, NioDatagramChannel, NioSctpChannel, NioSocketChannel
 * EventLoop: 控制流、多线程处理、并发，用于处理连接的生命周期中所发生的事件。
 * ChannelFuture: 异步通知。Netty提供了ChannelFuture接口，通过addListener()方法注册ChannelFutureListener，以便在操作完成时得到通知。
 * <p>
 * 2.3 管理数据流及执行应用程序处理逻辑的组件
 * ChannelHandler: 处理入站和出站数据。ChannelInboundHandler处理入站操作(常见的实现类：SimpleChannelInboundHandler<T> ), ChannelOutboundHandler处理出站操作。程序员处理出入站数据的应用程序逻辑，都在ChannelHandler接口的实现里。
 * ChannelPipeline: 提供了 ChannelHandler 链的容器，并定义了用于在该链上传播人站和出站事件流的 API。当Channel 被创建时，它会被自动地分配到它专属的 ChannelPipeline。ChannelHandler 安装到 ChannelPipeline 中的过程如下所示：
 * 一个channelInitializer的实现被注册到了ServerBootstrap中
 * 当channelInitializer.initChannel()方法被调用时，ChannelInitializer将在ChannelPipeline 中安装一组自定义的 ChannelHandler;
 * ChannelInitializer 将它自己从 ChannelPipeline 中移除。
 */
@Slf4j
@AllArgsConstructor
public class DiscardServer {

    public static final int SERVER_PORT = 8811;

    private int port;

    public void run() throws Exception {
        /**
         * EventLoop: 控制流、多线程处理、并发，用于处理连接的生命周期中所发生的事件。
         *
         * NioEventLoopGroup 是一个处理 I/O 操作的多线程事件循环。Netty 为不同类型的传输提供了各种 EventLoopGroup 实现。
         * 在此示例中，我们将实现一个服务器端应用程序，因此将使用两个 NioEventLoopGroup。第一个，通常称为“老板”，接受传入连接。
         * 第二个，通常称为“worker”，一旦老板接受连接并将接受的连接注册到worker，就会处理接受连接的流量。
         * 使用了多少个线程以及如何将它们映射到创建的通道取决于 EventLoopGroup 实现，甚至可以通过构造函数进行配置。
         *
         * bossGroup 用于接收客户端连接，workerGroup 用于处理客户端连接的读写请求。
         * 两个都是无限循环。
         * bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数默认为 cpu 核心数 * 2。
         * bossGroup 和 workerGroup 的子线程(NioEventLoop)个数可以通过构造方法指定。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /**
             * ServerBootstrap: 用于服务器，绑定到一个本地端口，2个EventLoopGroup
             *
             * ServerBootstrap 是用于设置服务器的帮助程序类。
             * 您可以直接使用通道设置服务器。
             * 但是，请注意，这是一个繁琐的过程，在大多数情况下您不需要这样做。
             */
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    /**
                     * Channel: 对应Socket。
                     * 基本的IO操作(bind(), connet(), read(), write()), 依赖于底层网络传输，大大降低了直接使用Socket的复杂性。
                     * Channel接口常用的实现类有：EmbeddedChannel, LocalServerChannel, NioDatagramChannel, NioSctpChannel, NioSocketChannel
                     *
                     * 在这里，我们指定使用 NioServerSocketChannel 类，该类用于实例化新通道以接受传入连接。
                     */
                    .channel(NioServerSocketChannel.class) // (3)
                    /**
                     * 此处指定的处理程序将始终由新接受的 Channel 进行评估。
                     * ChannelInitializer 是一个特殊的处理程序，旨在帮助用户配置新的 Channel。
                     * 最有可能的是，您希望通过添加一些处理程序（例如实现网络应用程序）来配置新通道的 ChannelPipeline。
                     * 随着应用程序变得复杂，您可能会向管道添加更多处理程序，并最终将此匿名类提取到顶级类中。
                     * DiscardServerHandler 是我们自己定义的处理程序，用于处理实际的业务逻辑。
                     */
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            /**
                             * ChannelPipeline: 提供了 ChannelHandler 链的容器，并定义了用于在该链上传播人站和出站事件流的 API。当Channel 被创建时，它会被自动地分配到它专属的 ChannelPipeline。
                             * ChannelHandler 安装到 ChannelPipeline 中的过程如下所示：
                             *      一个channelInitializer的实现被注册到了ServerBootstrap中
                             *      当channelInitializer.initChannel()方法被调用时，ChannelInitializer将在ChannelPipeline 中安装一组自定义的 ChannelHandler;
                             *      ChannelInitializer 将它自己从 ChannelPipeline 中移除。
                             */
                            // ch.pipeline() 返回的是和这条连接相关的逻辑处理链，采用了责任链模式。
                            ChannelPipeline channelPipeline = ch.pipeline();
                            /**
                             * ChannelHandler: 处理入站和出站数据。
                             * ChannelInboundHandler处理入站操作(常见的实现类：SimpleChannelInboundHandler<T> ), ChannelOutboundHandler处理出站操作。
                             * 程序员处理出入站数据的应用程序逻辑，都在ChannelHandler接口的实现里。
                             */
                            channelPipeline.addLast(new DiscardServerHandler());
                        }
                    })
                    /**
                     * 您还可以设置特定于实现的参数。我们正在编写一个 TCP/IP 服务器，因此我们可以设置套接字选项，
                     * 例如 和 。请参考 ChannelOption 的 apidocs 和具体的 ChannelConfig 实现，
                     * 了解支持的 s.Channel tcpNoDelay keepAliveChannelOption
                     */
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                    /**
                     * 你注意到了吗？ 用于接受传入连接的 NioServerSocketChannel。
                     * 用于父 ServerChannel 接受的通道，在本例中为 NioSocketChannel。
                     * option() childOption() option() childOption()
                     */
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            /**
             * ChannelFuture: 异步通知。Netty提供了ChannelFuture接口，通过addListener()方法注册ChannelFutureListener，以便在操作完成时得到通知。
             *
             * 我们现在已经准备好了。剩下的就是绑定到端口并启动服务器。
             * 在这里，我们绑定到机器中所有 NIC（网络接口卡）的端口。
             * 现在，您可以根据需要多次调用该方法（使用不同的绑定地址）。8080 bind()
             */
            // 绑定并开始接受传入的连接。
            ChannelFuture f = b.bind(port).sync(); // (7)
            log.info("Server started.");
            f.addListener(
                    (GenericFutureListener<Future<Void>>) future
                            -> log.info("Server Bind future complete."));

            //等待，直到服务器套接字被关闭。
            //在本例中，这种情况不会发生，但是您可以优雅地这样做
            //关闭服务器
            f.channel().closeFuture().sync();
        } finally {
            // 优雅地关闭工作组，释放所有资源。
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new DiscardServer(SERVER_PORT).run();
    }
}